package rcpexplorer.views;

import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.eclipse.core.runtime.FileLocator;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IContributor;
import org.eclipse.core.runtime.IExtension;
import org.eclipse.core.runtime.IExtensionPoint;
import org.eclipse.core.runtime.IExtensionRegistry;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.spi.RegistryContributor;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.viewers.IStructuredContentProvider;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.ui.IWorkbenchActionConstants;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.ide.IDE;
import org.eclipse.ui.part.ViewPart;
import org.osgi.framework.Bundle;

import rcpexplorer.Activator;

public class BEPEBView extends ViewPart {
	
	static enum Mode { BUNDLE, EXTENSIONPOINT, EXTENSION };
	
	private Mode mode = Mode.BUNDLE;
	
	private TreeViewer viewer;
	
	class ViewContentProvider implements IStructuredContentProvider, 
										   ITreeContentProvider {

		public void inputChanged(Viewer v, Object oldInput, Object newInput) {
		}
		
		public void dispose() {
		}
		
		public Object[] getElements(Object parent) {
			if (parent.equals(getViewSite())) {
				if (mode == Mode.BUNDLE) {
					return bundleIdToBundelMap.values().toArray();
				} else if (mode == Mode.EXTENSIONPOINT) {
					IExtensionPoint[] extensionPoints = Platform.getExtensionRegistry().getExtensionPoints();
					Arrays.sort(extensionPoints, new Comparator<IExtensionPoint>() {
						public int compare(IExtensionPoint extensionPoint1, IExtensionPoint extensionPoint2) {
							return extensionPoint1.getUniqueIdentifier().compareTo(
									extensionPoint2.getUniqueIdentifier());
						}
					});
					return extensionPoints;
				} else if (mode == Mode.EXTENSION) {
					IExtension[] exs = extensions.toArray(new IExtension[0]);
					Arrays.sort(exs, new Comparator<IExtension>() {
						public int compare(IExtension extension1, IExtension extension2) {
							return extension1.getExtensionPointUniqueIdentifier().compareTo(
									extension2.getExtensionPointUniqueIdentifier());
						}
					});
					return exs;
				}
			}
			return getChildren(parent);
		}
		
		public Object getParent(Object child) {
			return null;
		}
		
		public Object[] getChildren(Object parent) {
			if (parent instanceof Bundle) {
				Bundle bundle = (Bundle) parent;
				return getBundleChildren(bundle);
			} else if (parent instanceof AsBundleOf) {
				AsBundleOf asContributorOf = (AsBundleOf) parent;
				return getBundleChildren(asContributorOf.bundle);
			} else if (parent instanceof AsBundleDependency) {
				AsBundleDependency asBundleDependency = (AsBundleDependency) parent;
				return getBundleChildren(asBundleDependency.bundle);
			} else if (parent instanceof IExtensionPoint) {
				IExtensionPoint extensionPoint = (IExtensionPoint) parent;
				return getExtensionPointChildren(extensionPoint);
			} else if (parent instanceof AsExtensionPointIn) {
				AsExtensionPointIn asExtensionPointIn = (AsExtensionPointIn) parent;
				return getExtensionPointChildren(asExtensionPointIn.extensionPoint);
			} else if (parent instanceof AsExtensionPointFor) {
				AsExtensionPointFor asExtensionPointFor = (AsExtensionPointFor) parent;
				return getExtensionPointChildren(asExtensionPointFor.extensionPoint);
			} else if (parent instanceof IExtension) {
				IExtension extension = (IExtension) parent;
				return getExtensionChildren(extension);
			} else if (parent instanceof AsExtensionIn) {
				AsExtensionIn asExtensionIn = (AsExtensionIn) parent;
				return getExtensionChildren(asExtensionIn.extension);
			} else if (parent instanceof AsExtensionOf) {
				AsExtensionOf asExtensionOf = (AsExtensionOf) parent;
				return getExtensionChildren(asExtensionOf.extension);
			} else if (parent instanceof IConfigurationElement) {
				IConfigurationElement configurationElement = (IConfigurationElement) parent;
				return getConfigurationElementChildren(configurationElement);
			}
			return new Object[0];
		}
		public boolean hasChildren(Object parent) {
			if (parent instanceof Attribute) {
				return false;
			}
			return true;
		}
	}
	
	@SuppressWarnings("unchecked")
	public Object[] getBundleChildren(Bundle bundle) {
		List children = new LinkedList();
		List<IExtensionPoint> extensionPoints = bundleToExtensionPoints.get(bundle);
		if (extensionPoints != null) {
			for (IExtensionPoint extensionPoint:extensionPoints) {
				children.add(new AsExtensionPointIn(extensionPoint));
			}
		}
		
		List<IExtension> extensions = bundleToExtensions.get(bundle);
		if (extensions != null) {
			for (IExtension extension : extensions) {
				children.add(new AsExtensionIn(extension));
			}
		}
		
		List<Bundle> requiredBundles = bundleToRequiredBundles.get(bundle);
		if (requiredBundles != null) {
			for (Bundle requiredBundle : requiredBundles) {
				children.add(new AsBundleDependency(requiredBundle));
			}
		}
		
		return children.toArray();
	}

    private static List<String> getRequiredBundles(String requiredBundlesString) {
        requiredBundlesString = processEmbeddedCommas(requiredBundlesString);
        String[] pd = requiredBundlesString.split(",");
        List<String> packages = new ArrayList<String>(pd.length);
        for (String s : pd) {
            String[] parts = s.split(";");
            if (parts.length > 0) {
                packages.add(parts[0]);
            }
        }
        return packages;
    }
    
    private static String processEmbeddedCommas(String ep) {
        StringBuilder sb = new StringBuilder(ep);
        
        boolean insideQuotes = false;
        for (int i=0; i < sb.length(); i++) {
            char c = sb.charAt(i);
            switch (c) {
            case '\"':
                insideQuotes = !insideQuotes;
                break;
            case ',':
                if (insideQuotes) {
                    sb.setCharAt(i, ' ');
                }                    
            }
        }
        return sb.toString();
    }
	@SuppressWarnings("unchecked")
	public Object[] getExtensionPointChildren(IExtensionPoint extensionPoint) {
		List children = new LinkedList();
		List<IExtension> extensions = Arrays.asList(extensionPoint.getExtensions());
		for (IExtension extension : extensions) {
			children.add(new AsExtensionOf(extension));
		}
		Bundle bundle = extensionPointToBundle.get(extensionPoint);
		if (bundle != null) {
			children.add(new AsBundleOf(bundle));
		}
		return children.toArray();
	}
	
	@SuppressWarnings("unchecked")
	public Object[] getExtensionChildren(IExtension extension) {
		List children = new LinkedList();
		
		children.addAll(Arrays.asList(extension.getConfigurationElements()));
		
		String extensionPointUniqueIdentifier = extension.getExtensionPointUniqueIdentifier();
		if (extensionPointUniqueIdentifier != null) {
			IExtensionPoint extensionPoint = uniqueIdToExtensionPoints.get(extensionPointUniqueIdentifier);
			if (extensionPoint != null) {
				children.add(new AsExtensionPointFor(extensionPoint));
			}
		}
		
		Bundle bundle = extensionToBundle.get(extension);
		if (bundle != null) {
			children.add(new AsBundleOf(bundle));
		}
		
		return children.toArray();
	}

	@SuppressWarnings("unchecked")
	public Object[] getConfigurationElementChildren(IConfigurationElement configurationElement) {
		List children = new LinkedList();
		children.addAll(Arrays.asList(configurationElement.getChildren()));
		
		String[] attributeNames = configurationElement.getAttributeNames();
		for (String attributeName : attributeNames) {
			String attribute = configurationElement.getAttribute(attributeName);
			children.add(new Attribute(attributeName, attribute));
		}
		
		return children.toArray();
	}
	
	class ViewLabelProvider extends LabelProvider {

		public String getText(Object obj) {
			if (obj instanceof Bundle) {
				Bundle bundle = (Bundle) obj;
				return getBundleText(bundle);
			} else if (obj instanceof AsBundleOf) {
				AsBundleOf asBundleOf = (AsBundleOf) obj;
				return getBundleText(asBundleOf.bundle);
			}  else if (obj instanceof AsBundleDependency) {
				AsBundleDependency asBundleDependency = (AsBundleDependency) obj;
				return getBundleText(asBundleDependency.bundle);
			} else if (obj instanceof IExtensionPoint) {
				IExtensionPoint extensionPoint = (IExtensionPoint) obj;
				return getExtensionPointText(extensionPoint);
			} else if (obj instanceof AsExtensionPointIn) {
				AsExtensionPointIn asExtensionPointIn = (AsExtensionPointIn) obj;
				return getExtensionPointText(asExtensionPointIn.extensionPoint);
			} else if (obj instanceof AsExtensionPointFor) {
				AsExtensionPointFor asExtensionPointFor = (AsExtensionPointFor) obj;
				return getExtensionPointText(asExtensionPointFor.extensionPoint);
			} else if (obj instanceof IExtension) {
				IExtension extension = (IExtension) obj;
				return getExtensionText(extension);
			} else if (obj instanceof AsExtensionIn) {
				AsExtensionIn asExtensionIn = (AsExtensionIn) obj;
				return getExtensionText(asExtensionIn.extension);
			} else if (obj instanceof AsExtensionOf) {
				AsExtensionOf asExtensionOf = (AsExtensionOf) obj;
				return getExtensionText(asExtensionOf.extension);
			} else if (obj instanceof IConfigurationElement) {
				IConfigurationElement configurationElement = (IConfigurationElement) obj;
				String value = configurationElement.getValue();
				return configurationElement.getName() + (value == null ? "" : ":" + value);
			} else if (obj instanceof Attribute) {
				Attribute attribute = (Attribute) obj;
				return attribute.toString();
			}
			return obj.toString();
		}
		
		private String getBundleText(Bundle bundle) {
			int state = bundle.getState();
			String stateLabel = "";
			switch (state) {
			case Bundle.STARTING:
				stateLabel = "STARTING";
				break;
			case Bundle.INSTALLED:
				stateLabel = "INSTALLED";
				break;				
			case Bundle.RESOLVED:
				stateLabel = "RESOLVED";
				break;
			case Bundle.ACTIVE:
				stateLabel = "ACTIVE";
				break;
			case Bundle.STOPPING:
				stateLabel = "STOPPING";
				break;
			case Bundle.UNINSTALLED:
				stateLabel = "UNINSTALLED";
				break;
			}
			String location = bundle.getLocation();
			if (location != null && location.startsWith("reference:")) {
				location = location.substring(10);
			}
			if (location == null) {
				location = "";
			}
			return bundle.getSymbolicName() + " [" + stateLabel + "] " + location;
		}
		
		private String getExtensionPointText(IExtensionPoint extensionPoint) {
			Bundle bundle = extensionPointToBundle.get(extensionPoint);
			return extensionPoint.getUniqueIdentifier() +
				(bundle == null ? "" : " [" + bundle.getSymbolicName() + "]");
		}

		private String getExtensionText(IExtension extension) {
			Bundle bundle = extensionToBundle.get(extension);				
			return "Implements " + 
				extension.getExtensionPointUniqueIdentifier() +
				(bundle == null ? "" : " [" + bundle.getSymbolicName() + "]");
		}
		
		public Image getImage(Object obj) {
			if (obj instanceof Bundle) {
				return Activator.getDefault().getImageRegistry().get(Activator.BUNDLE);
			} else if (obj instanceof AsBundleOf) {
				return Activator.getDefault().getImageRegistry().get(Activator.BUNDLE_OF);
			} else if (obj instanceof AsBundleDependency) {
				return Activator.getDefault().getImageRegistry().get(Activator.BUNDLE_OF);
			} else if (obj instanceof IExtensionPoint) {
				return Activator.getDefault().getImageRegistry().get(Activator.EXTENSION_POINT);
			}  else if (obj instanceof AsExtensionPointIn) {
				return Activator.getDefault().getImageRegistry().get(Activator.EXTENSION_POINT_IN);
			}  else if (obj instanceof AsExtensionPointFor) {
				return Activator.getDefault().getImageRegistry().get(Activator.EXTENSION_POINT_FOR);
			} else if (obj instanceof IExtension) {
				return Activator.getDefault().getImageRegistry().get(Activator.EXTENSION);
			} else if (obj instanceof AsExtensionIn) {
				return Activator.getDefault().getImageRegistry().get(Activator.EXTENSION_IN);
			} else if (obj instanceof AsExtensionOf) {
				return Activator.getDefault().getImageRegistry().get(Activator.EXTENSION_OF);
			} else if (obj instanceof IConfigurationElement) {
				return Activator.getDefault().getImageRegistry().get(Activator.ELEMENT);
			} else if (obj instanceof Attribute) {
				return Activator.getDefault().getImageRegistry().get(Activator.ATTRIBUTE);
			}
			return null;
		}
		
	}

	/**
	 * The constructor.
	 */
	public BEPEBView() {
	}

	public Mode getMode() {
		return mode;
	}
	
	public void setMode(Mode mode) {
		Mode oldMode = this.mode;
		this.mode = mode;
		if (oldMode != this.mode) {
			viewer.setInput(getViewSite());
		}
	}
	
	TreeViewer getViewer() {
		return viewer;
	}
	
	/**
	 * This is a callback that will allow us
	 * to create the viewer and initialize it.
	 */
	public void createPartControl(Composite parent) {
		init();
		viewer = new TreeViewer(parent, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL);
		viewer.setContentProvider(new ViewContentProvider());
		viewer.setLabelProvider(new ViewLabelProvider());
		viewer.setInput(getViewSite());
		getSite().setSelectionProvider(viewer);
		
		MenuManager menuMgr = new MenuManager("#PopupMenu");
        menuMgr.setRemoveAllWhenShown(false);
        menuMgr.add(new Separator(IWorkbenchActionConstants.MB_ADDITIONS));
        Menu menu = menuMgr.createContextMenu(viewer.getControl());
        viewer.getControl().setMenu(menu);
        getSite().registerContextMenu("popup", menuMgr, viewer);

	}

	/**
	 * Passing the focus request to the viewer's control.
	 */
	public void setFocus() {
		viewer.getControl().setFocus();
	}
	
	// Implementation
	private Map<String, Bundle> bundleIdToBundelMap = new LinkedHashMap<String, Bundle>();

	private Map<String, Bundle> symbolicNameToBundelMap = new HashMap<String, Bundle>();

	private Map<Bundle, List<Bundle>> bundleToRequiredBundles = new HashMap<Bundle, List<Bundle>>();
	
	private Map<Bundle, List<IExtensionPoint>> bundleToExtensionPoints = new HashMap<Bundle, List<IExtensionPoint>>();
	private Map<Bundle, List<IExtension>> bundleToExtensions = new HashMap<Bundle, List<IExtension>>();

	private Map<String, IExtensionPoint> uniqueIdToExtensionPoints = new HashMap<String, IExtensionPoint>();

	private Map<IExtensionPoint, Bundle> extensionPointToBundle = new HashMap<IExtensionPoint, Bundle>();
	private Map<IExtension, Bundle> extensionToBundle = new HashMap<IExtension, Bundle>();
	
	private Set<IExtension> extensions = new LinkedHashSet<IExtension>();
	
	private void init() {
		Bundle[] bundles = Activator.getDefault().getBundle().getBundleContext().getBundles();
		Arrays.sort(bundles, new Comparator<Bundle>() {
			public int compare(Bundle bundle1, Bundle bundle2) {
				return bundle1.getSymbolicName().compareTo(
						bundle2.getSymbolicName());
			}
		});
		
		for (Bundle bundle : bundles) {
			bundleIdToBundelMap
					.put(Long.toString(bundle.getBundleId()), bundle);
			symbolicNameToBundelMap.put(bundle.getSymbolicName(), bundle);
		}
		
		for (Bundle bundle : bundles) {
			Object requiredBundlesString = bundle.getHeaders().get("Require-Bundle");
			if (requiredBundlesString instanceof String) {
				List<String> requiredBundleNames = getRequiredBundles((String) requiredBundlesString);
				for (String requiredBundleName : requiredBundleNames) {
					Bundle requiredBundle = symbolicNameToBundelMap.get(requiredBundleName);
					if (requiredBundle != null) {
						List<Bundle> requiredBundles = bundleToRequiredBundles.get(bundle);
						if (requiredBundles == null) {
							requiredBundles = new LinkedList<Bundle>();
							bundleToRequiredBundles.put(bundle, requiredBundles);
						}
						requiredBundles.add(requiredBundle);
					}
				}
			}
		}

		IExtensionRegistry extensionRegistry = Platform.getExtensionRegistry();

		IExtensionPoint[] extensionPoints = extensionRegistry.getExtensionPoints();
		for (IExtensionPoint extensionPoint : extensionPoints) {
			String uniqueIdentifier = extensionPoint.getUniqueIdentifier();
			if (uniqueIdentifier != null) {
				uniqueIdToExtensionPoints.put(uniqueIdentifier, extensionPoint);
			}
			IContributor contributor = extensionPoint.getContributor();
			if (contributor instanceof RegistryContributor) {
				RegistryContributor registryContributor = (RegistryContributor) contributor;
				String contributingBundleId = registryContributor.getActualId();
				Bundle bundle = bundleIdToBundelMap.get(contributingBundleId);
				assert bundle != null;
				extensionPointToBundle.put(extensionPoint, bundle);
				List<IExtensionPoint> bundlesExtensionPoints = bundleToExtensionPoints
						.get(bundle);
				if (bundlesExtensionPoints == null) {
					bundlesExtensionPoints = new LinkedList<IExtensionPoint>();
					bundleToExtensionPoints.put(bundle, bundlesExtensionPoints);
				}
				bundlesExtensionPoints.add(extensionPoint);
			}
			for (IExtension extension : extensionPoint.getExtensions()) {
				extensions.add(extension);
				IContributor extensionContributor = extension.getContributor();
				if (extensionContributor instanceof RegistryContributor) {
					RegistryContributor registryExtensionContributor = (RegistryContributor) extensionContributor;
					String contributingBundleId = registryExtensionContributor.getActualId();
					Bundle bundle = bundleIdToBundelMap.get(contributingBundleId);
					assert bundle != null;
					
					extensionToBundle.put(extension, bundle);
					List<IExtension> bundlesExtensions = bundleToExtensions
							.get(bundle);
					if (bundlesExtensions == null) {
						bundlesExtensions = new LinkedList<IExtension>();
						bundleToExtensions.put(bundle, bundlesExtensions);
					}
					bundlesExtensions.add(extension);
				}
			}
		}
	}
	
	void openPluginXml(Bundle bundle) {
		if (bundle == null) {
			return;
		}
		try {
			URL url = null;
			try {
				URL entry = bundle.getEntry("META-INF/MANIFEST.MF");
				if (entry != null) {
					FileLocator.toFileURL(entry);
				}
			} catch (IOException el) {
			}
			URL entry = bundle.getEntry("plugin.xml");
			if (entry == null) {
				entry = bundle.getEntry("fragment.xml"); // may be a fragment
			} 
			if (entry != null) {
				url = FileLocator.toFileURL(entry);
			}
			
			if (url != null) {
				IDE.openEditor(PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage(),
						new URI(url.toString().replaceAll(" ", "%20")),
						"org.eclipse.pde.ui.manifestEditor",
						true);
			}
		} catch (IOException e1) {
		} catch (PartInitException el) {
		} catch (URISyntaxException el) {
		}
	}
	
	void openPluginXml(IExtensionPoint extensionPoint) {
		if (extensionPoint == null) {
			return;
		}
		openPluginXml(extensionPointToBundle.get(extensionPoint));
	}
	
	void openPluginXml(IExtension extension) {
		if (extension == null) {
			return;
		}
		openPluginXml(extensionToBundle.get(extension));
	}
	
	static void toXml(IExtension extension, StringBuilder stringBuilder) {
		if (extension == null) {
			return;
		}
		stringBuilder.append("<extension\n\tpoint=\"" + extension.getExtensionPointUniqueIdentifier() + "\"");
		String uniqueIdentifier = extension.getUniqueIdentifier();
		if (uniqueIdentifier != null) {
			stringBuilder.append("\n\tid=\"" + uniqueIdentifier + "\"");
		}
		stringBuilder.append(
		">\n");
		IConfigurationElement[] children = extension.getConfigurationElements();
		for (IConfigurationElement child : children) {
			toXml(child, stringBuilder);
		}
		stringBuilder.append("</extension>\n");
	}
	
	static void toXml(IConfigurationElement configurationElement, StringBuilder stringBuilder) {
		if (configurationElement == null) {
			return;
		}
		String name = configurationElement.getName();
		stringBuilder.append(
				"<" +
				name);
		String[] attributeNames = configurationElement.getAttributeNames();
		for (String attributeName : attributeNames) {
			String value = configurationElement.getAttribute(attributeName);
			if (value != null) {
				stringBuilder.append("\n\t" + attributeName + "=\"" + value + "\"");
			}
		}
		stringBuilder.append(
				">\n");
		String value = configurationElement.getValue();
		if (value != null) {
			stringBuilder.append(value);
		}
		IConfigurationElement[] children = configurationElement.getChildren();
		for (IConfigurationElement child : children) {
			toXml(child, stringBuilder);
		}
		stringBuilder.append(
				"</" +
				name
				+ ">\n");
	}
}